#pragma once

#include <iostream>
#include <sys/select.h>
#include "sock.hpp"

namespace selectServer_ns
{
    static const uint16_t defaultport = 8080;

    class SelectServer
    {
        static const int fdnum = sizeof(fd_set) * 8;
        static const int defaultfd = -1;
    public:
        SelectServer(const uint16_t &port = defaultport)
            : _listensock(-1), _port(port), _fdarray(nullptr)
        {
        }

        void init()
        {
            _listensock = Sock::Socket();
            Sock::Bind(_listensock, _port);
            Sock::Listen(_listensock);

            // 问题来了，要给这个数组new多大空间呢？
            // 要根据fd_set这个类型进行设置，这个fd_set的上限决定了select最多检测多少个文件描述符
            // std::cout << "sizeof(fd_set): " << sizeof(fd_set) << std::endl;             // 128字节
            // std::cout << "sizeof(fd_set) * 8: " << sizeof(fd_set) * 8 << std::endl;     // 因为这是一个位图结构，所以每个字节可以表示8个位置
            _fdarray = new int[fdnum];

            // 进行初始化清空
            for(int i = 0; i < fdnum; i++) _fdarray[i] = defaultfd;

            // 设置监听套接字
            _fdarray[0] = _listensock;
        }

        void start()
        {
            // fd_set rfds;
            // FD_ZERO(&rfds);
            // FD_SET(_listensock, &rfds);
            // 这部分代码也不能写在这里，因为如果有事件就绪了，我们拿走这个事件后，还得重新把对应文件描述符集中依旧要等待的fd置为1

            // struct timeval timeout = {2, 0};     // 不能写在这个地方，因为式输入输出型参数，阻塞2秒之后就清空了，变成了0了
            for (;;)
            {
                struct timeval timeout = {2, 0}; // 写在这里才是正确的设置阻塞时间

                fd_set rfds;
                FD_ZERO(&rfds);
                // FD_SET(_listensock, &rfds);

                int maxfd = _fdarray[0];

                for(int i = 0; i < fdnum; i++)
                {
                    if(_fdarray[i] == defaultfd)
                        continue;
                    FD_SET(_fdarray[i], &rfds);         // 将合法文件描述符添加到事件集合中
                    maxfd = max(maxfd, _fdarray[i]);    // 更新所有fd中最大的fd
                }

                // listensock可以交给selec监管，listen的连接就绪可以归类为 读事件就绪
                // int n = select(_listensock + 1, nullptr, nullptr, nullptr, &timeout);

                // 一般而言，要是用select，需要程序员自己维护一个保存所有合法fd的数组！
                // int n = select(_listensock + 1, &rfds, nullptr, nullptr, nullptr); // 阻塞式等待
                int n = select(maxfd + 1, &rfds, nullptr, nullptr, nullptr); // 阻塞式等待

                switch (n)
                {
                case 0:
                    // 超时了，在阻塞的时间段内没有事件就绪，变为非阻塞
                    logMessage(NORMAL, "timeout...");
                    break;
                case -1:
                    // select调用失败
                    logMessage(WARNING, "errno: %d, err_msg: %s", errno, strerror(errno));
                    break;
                default:
                    // 事件就绪，目前只有一个监听事件
                    logMessage(NORMAL, "get a new link!");
                    HandlerEvent(rfds);
                    break;
                }

                PrintFdlist();

                // 这里其实不能直接accep，如果accept了，那还是阻塞式的，跟之前一样，现在让select去监管，有连接来了再通知accept去获取
                // accept = 等 + 获取
                // std::string clientip;
                // uint16_t clientport;
                // int sock = Sock::Accept(_listensock, &clientip, &clientport);
                // if(sock < 0) continue;
            }
        }

        void PrintFdlist()
        {
            std::cout << "fd_list: ";
            for(int i = 0; i < fdnum; i++)
            {
                if(_fdarray[i] != defaultfd)
                    std::cout << _fdarray[i] << " ";
            }
            std::cout << std::endl;
        }

        void HandlerEvent(fd_set rfds)
        {
            // 目前一定是listen套接字有事件就绪了
            if (FD_ISSET(_listensock, &rfds))
            {
                // 走到这，一定是select告诉我listensock有事件就绪了
                std::string clientip;
                uint16_t clientport;

                // 走到这，accept就不会阻塞了，因为此时一定有事件就绪了，轮到你accept干活了
                int sock = Sock::Accept(_listensock, &clientip, &clientport);
                if (sock < 0)
                    return;

                logMessage(NORMAL, "accept success! [%s: %d]", clientip.c_str(), clientport);

                // 走到这，就能直接recv/read吗，不行！如果read/recv了，这下又成了阻塞式IO了
                // 所以也是要交给select去检测事件是否就绪的

                // 将新的sockfd托管给select，本质就是添加到_fdarray数组中
                int i = 0;
                for(; i < fdnum; i++)
                {
                    // 找到一个非法的位置，然后进行插入
                    if(_fdarray[i] != defaultfd)
                        continue;
                    else
                        break;
                }

                if(i == fdnum)
                {
                    // 说明服务器承载能力到达极限
                    logMessage(WARNING, "server is full, please wait!");
                    close(sock);
                }
                else
                {
                    // 走到这，说明有了空位置，可以让select帮我们检测
                    _fdarray[i] = sock;
                }
            }
        }

        ~SelectServer()
        {
            if (_listensock > 0)
                close(_listensock);
            if(_fdarray)
                delete []_fdarray;
        }

    private:
        int _listensock;
        uint16_t _port;
        int *_fdarray;
    };

} // namespace selectServer_ns
